/*
 * Copyright 2011 Király Attila
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.young.word.cage;

import java.util.Random;

/**
 * A simple random String generator that can be used to generate tokens for the
 * captcha images.
 * <p>
 * In its default mode instances of this class generate words from English lower
 * cased letters where vowels and consonants are alternating.
 *
 * @author akiraly
 */
public class RandomTokenGenerator implements IGenerator<String> {

  /**
   * Default minimum length of token.
   */
  protected static final int DEFAULT_TOKEN_LEN_MIN = 8;

  /**
   * Default maximum length of token is {@link #DEFAULT_TOKEN_LEN_MIN} +
   * {@value #DEFAULT_TOKEN_LEN_DELTA}.
   */
  protected static final int DEFAULT_TOKEN_LEN_DELTA = 2;

  private final IGeneratorFactory<Character> characterGeneratorFactory;
  private final int minLength;
  private final int delta;
  private final Random rnd;

  /**
   * Constructor.
   */
  public RandomTokenGenerator() {
    this(null);
  }

  /**
   * Constructor.
   *
   * @param rnd random generator to be used, can be null
   */
  public RandomTokenGenerator(Random rnd) {
    this(rnd, DEFAULT_TOKEN_LEN_MIN, DEFAULT_TOKEN_LEN_DELTA);
  }

  /**
   * Constructor.
   *
   * @param length the length of the generated words
   * @param rnd    random generator to be used, can be null
   */
  public RandomTokenGenerator(Random rnd, int length) {
    this(rnd, length, 0);
  }

  /**
   * Constructor.
   *
   * @param rnd       random generator to be used, can be null
   * @param minLength the minimum length of the generated words
   * @param delta     minLength + delta is the maximum length of the generated
   *                  words
   */
  public RandomTokenGenerator(Random rnd, int minLength, int delta) {
    this(rnd, new RandomCharacterGeneratorFactory(rnd != null ? rnd
        : new Random()), minLength, delta);
  }

  /**
   * Constructor.
   *
   * @param rnd                       random generator to be used, can be null
   * @param characterGeneratorFactory character generator factory to be used for the actual
   *                                  character creation, can be null
   * @param minLength                 the minimum length of the generated words,
   * @param delta                     minLength + delta is the maximum length of the generated
   *                                  words
   */
  public RandomTokenGenerator(Random rnd,
                              IGeneratorFactory<Character> characterGeneratorFactory,
                              int minLength, int delta) {
    this.characterGeneratorFactory = characterGeneratorFactory != null ? characterGeneratorFactory
        : new RandomCharacterGeneratorFactory();
    this.minLength = Math.abs(minLength);
    this.delta = Math.abs(delta);
    this.rnd = rnd != null ? rnd : new Random();
  }

  @Override
  public String next() {
    final int length = (delta <= 1 ? 0 : rnd.nextInt(delta) + 1)
        + minLength;
    final char[] word = new char[length];
    final IGenerator<Character> generator = characterGeneratorFactory
        .next();

    for (int fi = 0; fi < word.length; fi++) {
      word[fi] = generator.next();
    }

    return new String(word);
  }

  /**
   * @return character generator factory used by this class, not null
   */
  public IGeneratorFactory<Character> getCharacterGeneratorFactory() {
    return characterGeneratorFactory;
  }

  /**
   * @return minimum length of generated tokens
   */
  public int getMinLength() {
    return minLength;
  }

  /**
   * @return maximum length difference to add to the minimum length
   */
  public int getDelta() {
    return delta;
  }
}
